#include "StdAfx.h"
#include "Renderer.h"
#include "Scene.h"
#include "Random.h"
#include "EasyBMP/EasyBMP.h"

CRenderer::CRenderer(void)
: m_pFrameBuffer(NULL)
, m_pDepthBuffer(NULL)
, m_pTexture(NULL)
{
}

CRenderer::~CRenderer(void)
{
	if (m_pFrameBuffer)
		delete[] m_pFrameBuffer;

	if (m_pDepthBuffer)
		delete[] m_pDepthBuffer;

	if (m_pTexture)
		delete m_pTexture;
}

void CRenderer::Init()
{
	m_pFrameBuffer = new unsigned char[WINDOW_WIDTH * WINDOW_HEIGHT * 4];
	m_pDepthBuffer = new DATA_TYPE[WINDOW_WIDTH * WINDOW_HEIGHT];

	m_vWorldPositions.resize(WINDOW_WIDTH * WINDOW_HEIGHT);
	m_vTransformedPositions.resize(WINDOW_WIDTH * WINDOW_HEIGHT);
	m_vColors.resize(WINDOW_WIDTH * WINDOW_HEIGHT);
	m_vNormals.resize(WINDOW_WIDTH * WINDOW_HEIGHT);
	m_vTextureUVs.resize(WINDOW_WIDTH * WINDOW_HEIGHT);

	bool bInput2D = CConstants::GetInstance().IsTrue("INPUT_2D");
	for (int i = 0; i < WINDOW_WIDTH * WINDOW_HEIGHT; ++i)
	{
		m_vTransformedPositions[i].z = numeric_limits<DATA_TYPE>::min();
		m_vColors[i] = bInput2D ? CVector4(0.f, 0.f, 0.f, 1.f) : CVector4(0.7f, 0.7f, 0.7f, 1.f);
	}

	if (CConstants::GetInstance().IsTrue("USE_TEXTURE"))
	{
		m_pTexture = new BMP();
		if (!m_pTexture->ReadFromFile(CConstants::GetInstance().GetValue("TEXTURE_FILE").c_str()))
		{
			delete m_pTexture;
			m_pTexture = NULL;
		}
	}

	SetupMatricesForP2();
}

void CRenderer::Render(CScene* pScene)
{
	if (pScene->GetUpdated() == false)
		return;
	pScene->SetUpdated(false);

	bool bInput2D = CConstants::GetInstance().IsTrue("INPUT_2D");
	DWORD bgColor = bInput2D ? Color2RGBA(0.f, 0.f, 0.f, 1.f) : Color2RGBA(0.7f, 0.7f, 0.7f, 1.f);
	DWORD* pColorBuffer = (DWORD*)m_pFrameBuffer;
	DATA_TYPE* pDepthBuffer = m_pDepthBuffer;
	for (int i = 0; i < WINDOW_WIDTH * WINDOW_HEIGHT; ++i)
	{
		*pColorBuffer = bgColor;
		++pColorBuffer;

		*pDepthBuffer = -numeric_limits<DATA_TYPE>::max();
		++pDepthBuffer;
	}
	
	const vector<CPrimitivePtr>& vPrimitives = pScene->GetPrimitives();
	CMatrix4 matTransform = matViewport * matProjection * matView;
	for (unsigned int i = 0; i < vPrimitives.size(); ++i)
	{
		vPrimitives[i]->Transform(matTransform);
	}

	// Rasterization
	for (unsigned int i = 0; i < vPrimitives.size(); ++i)
	{
		vPrimitives[i]->Rasterize(this);
	}

	// Color Evaluation
	for (int j = 0; j < WINDOW_HEIGHT; ++j)
	{
		for (int i = 0; i < WINDOW_WIDTH; ++i)
		{
			int iIndex = i + j * WINDOW_WIDTH;

			RenderFragment(iIndex);
		}
	}
}
void CRenderer::RenderFragment(int iIndex)
{
	bool bInput2D = CConstants::GetInstance().IsTrue("INPUT_2D");
	bool bUseTexture = CConstants::GetInstance().IsTrue("USE_TEXTURE");
	CVector4 vShadeValue;

	if (bInput2D)
	{
		vShadeValue = m_vColors.at(iIndex);
	}
	else
	{
		// GetValues
		CVector4 vWorldPosition = m_vWorldPositions.at(iIndex);
		CVector4 vColor = m_vColors.at(iIndex);
		CVector4 vNormal = m_vNormals.at(iIndex);

		// Compute Lighting
		const CVector4 vLightColor = CVector4(1.f, 1.f, 1.f, 1.f);
		const CVector4 vRayDir = 
			CConstants::GetInstance().IsTrue("USE_PERSPECTIVE") ? (vWorldPosition - CAMERA_POS).Normalize() : (VIEW_POS - CAMERA_POS).Normalize();

		const CVector4 l = -LIGHT_DIR;
		const CVector4 h = (l - vRayDir).Normalize();

		DATA_TYPE fAmbientColor = AMBIENT_COEFF;
		DATA_TYPE fDiffuseColor = __max(0, vNormal.Dot(l));
		DATA_TYPE fSpecularColor = powf(__max(0, vNormal.Dot(h)), PHONG_EXPONENT);
		
		vShadeValue = vColor * vLightColor * (fAmbientColor + fDiffuseColor);
		vShadeValue = vShadeValue + vLightColor * fSpecularColor;
		vShadeValue.w = 1.f;
	}

	if (bUseTexture)
	{
		CVector4 vTextureUV = m_vTextureUVs.at(iIndex);
		vShadeValue = vShadeValue * SampleTexture(vTextureUV);
	}

	m_pFrameBuffer[iIndex * 4] = (unsigned char)clamp((vShadeValue.x * 255.f), 0.f, 255.f);
	m_pFrameBuffer[iIndex * 4 + 1] = (unsigned char)clamp((vShadeValue.y * 255.f), 0.f, 255.f);
	m_pFrameBuffer[iIndex * 4 + 2] = (unsigned char)clamp((vShadeValue.z * 255.f), 0.f, 255.f);
	m_pFrameBuffer[iIndex * 4 + 3] = 255;
}

void CRenderer::UpdateFrame()
{
	glPixelStorei(GL_PACK_ALIGNMENT, 1);
	glDrawPixels(WINDOW_WIDTH, WINDOW_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, m_pFrameBuffer);
}

void CRenderer::SetupMatricesForP1()
{
	matView = CMatrix4::IdentityMatrix();
	matProjection = CMatrix4::IdentityMatrix();

	matViewport = CMatrix4::ScalingMatrix(WINDOW_WIDTH / 5.f, WINDOW_HEIGHT / 5.f, 1.f) *
				  CMatrix4::TranslationMatrix(1.f, 1.f, 0.f);
}

void CRenderer::SetupMatricesForP2()
{
	bool bInput2D = CConstants::GetInstance().IsTrue("INPUT_2D");

	if (bInput2D)
	{
		matView = CMatrix4::IdentityMatrix();
		matProjection = CMatrix4::IdentityMatrix();

		matViewport = CMatrix4::ScalingMatrix(WINDOW_WIDTH / 5.f, WINDOW_HEIGHT / 5.f, 1.f)
			* CMatrix4::TranslationMatrix(1.f, 1.f, 0.f);
	}
	else
	{
		// view matrix
		CVector4 vCameraDir = (CAMERA_POS - VIEW_POS).Normalize();
		CVector4 vCameraRight = CAMERA_UP.Cross3(vCameraDir).Normalize();
		CVector4 vCameraUp = vCameraDir.Cross3(vCameraRight).Normalize();
		for (int i = 0; i < 3; ++i)
		{
			matView[0][i] = vCameraRight[i];
			matView[1][i] = vCameraUp[i];
			matView[2][i] = vCameraDir[i];
		}
		matView[0][3] = -vCameraRight.Dot(CAMERA_POS);
		matView[1][3] = -vCameraUp.Dot(CAMERA_POS);
		matView[2][3] = -vCameraDir.Dot(CAMERA_POS);
		matView[3][3] = 1.f;

		// perspective projection matrix
		DATA_TYPE fHorizontalWidth = -NEAR_PLANE * tanf(DEG2RAD(FOV / 2.f));
		if (CConstants::GetInstance().IsTrue("USE_PERSPECTIVE"))
		{
			matProjection[0][0] = NEAR_PLANE / fHorizontalWidth;
			matProjection[1][1] = NEAR_PLANE / fHorizontalWidth;
			matProjection[2][2] = (FAR_PLANE + NEAR_PLANE) / (NEAR_PLANE - FAR_PLANE);
			matProjection[2][3] = 2*FAR_PLANE*NEAR_PLANE / (FAR_PLANE - NEAR_PLANE);
			matProjection[3][2] = 1.f;
		}
		else
		{
			matProjection[0][0] = 1.f / fHorizontalWidth;
			matProjection[1][1] = 1.f / fHorizontalWidth;
			matProjection[2][2] = 2.f / (NEAR_PLANE - FAR_PLANE);
			matProjection[2][3] = -(NEAR_PLANE + FAR_PLANE)/ (NEAR_PLANE - FAR_PLANE);
			matProjection[3][3] = 1.f;
		}

		matViewport = CMatrix4::ScalingMatrix(WINDOW_WIDTH / 2.f, WINDOW_HEIGHT / 2.f, 1.f);
		matViewport[0][3] = (WINDOW_WIDTH - 1.f) / 2.f;
		matViewport[1][3] = (WINDOW_HEIGHT - 1.f) / 2.f;
	}
}

bool CRenderer::ZTest(int iIndex, DATA_TYPE fValue)
{
	if (fValue > m_pDepthBuffer[iIndex])
	{
		m_pDepthBuffer[iIndex] = fValue;
		return true;
	}
	return false;
}

CVector4 CRenderer::SampleTexture(const CVector4& vTextureUV)
{
	CVector4 ret(1.f, 1.f, 1.f, 1.f);

	if (m_pTexture)
	{
		int x = (int)(vTextureUV.x * (m_pTexture->TellWidth() - 1));
		int y = (int)((1.f - vTextureUV.y) * (m_pTexture->TellHeight() - 1));
		RGBApixel pixel = m_pTexture->GetPixel(x, y);

		ret = CVector4(pixel.Red / 255.f, pixel.Green / 255.f, pixel.Blue / 255.f, 1.f);
	}
	return  ret;
}